<?php

/*
 * This file is part of the php-phantomjs.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Ling\PhantomJs;

use Ling\PhantomJs\Http\AbstractRequest;
use Ling\PhantomJs\Procedure\ProcedureLoaderInterface;
use Ling\PhantomJs\Procedure\ProcedureCompilerInterface;
use Ling\PhantomJs\Http\MessageFactoryInterface;
use Ling\PhantomJs\Http\ResponseInterface;
use Ling\PhantomJs\DependencyInjection\ServiceContainer;

/**
 * PHP PhantomJs
 *
 * @author Jon Wenmoth <contact@jonnyw.me>
 */
class Client implements ClientInterface
{
    /**
     * Client.
     *
     * @var Client
     * @access private
     */
    private static Client $instance;

    /**
     * PhantomJs engine.
     *
     * @var Engine
     * @access protected
     */
    protected Engine $engine;

    /**
     * Procedure loader.
     *
     * @var ProcedureLoaderInterface
     * @access protected
     */
    protected ProcedureLoaderInterface $procedureLoader;

    /**
     * Procedure validator.
     *
     * @var ProcedureCompilerInterface
     * @access protected
     */
    protected ProcedureCompilerInterface $procedureCompiler;

    /**
     * Message factory.
     *
     * @var MessageFactoryInterface
     * @access protected
     */
    protected MessageFactoryInterface $messageFactory;

    /**
     * Procedure template
     *
     * @var string
     * @access protected
     */
    protected string $procedure;

    /**
     * Internal constructor
     *
     * @access public
     * @param Engine $engine
     * @param ProcedureLoaderInterface $procedureLoader
     * @param ProcedureCompilerInterface $procedureCompiler
     * @param MessageFactoryInterface $messageFactory
     * @return void
     */
    public function __construct(Engine $engine, ProcedureLoaderInterface $procedureLoader, ProcedureCompilerInterface $procedureCompiler, MessageFactoryInterface $messageFactory)
    {
        $this->engine            = $engine;
        $this->procedureLoader   = $procedureLoader;
        $this->procedureCompiler = $procedureCompiler;
        $this->messageFactory    = $messageFactory;
        $this->procedure         = 'http_default';
    }

    /**
     * Get singleton instance
     *
     * @access public
     * @return Client
     * @throws \Exception
     */
    public static function getInstance(): Client
    {
        if (!isset(self::$instance) || !self::$instance instanceof ClientInterface) {
            $serviceContainer = ServiceContainer::getInstance();
            self::$instance = new static(
                $serviceContainer->get('engine'),
                $serviceContainer->get('procedure_loader'),
                $serviceContainer->get('procedure_compiler'),
                $serviceContainer->get('message_factory')
            );
        }

        return self::$instance;
    }

    /**
     * Get PhantomJs engine.
     *
     * @access public
     * @return Engine
     */
    public function getEngine(): Engine
    {
        return $this->engine;
    }

    /**
     * Get message factory instance
     *
     * @access public
     * @return MessageFactoryInterface
     */
    public function getMessageFactory(): MessageFactoryInterface
    {
        return $this->messageFactory;
    }

    /**
     * Get procedure loader instance
     *
     * @access public
     * @return ProcedureLoaderInterface
     */
    public function getProcedureLoader(): ProcedureLoaderInterface
    {
        return $this->procedureLoader;
    }

    /**
     * Send request
     *
     * @access public
     * @param AbstractRequest $request
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    public function send(AbstractRequest $request, ResponseInterface $response): ResponseInterface
    {
        $procedure = $this->procedureLoader->load($this->procedure);

        $this->procedureCompiler->compile($procedure, $request);

        $procedure->run($request, $response);

        return $response;
    }

    /**
     * Get log.
     *
     * @access public
     * @return string
     */
    public function getLog(): string
    {
        return $this->getEngine()->getLog();
    }

    /**
     * Set procedure template.
     *
     * @access public
     * @param string $procedure
     * @return Client
     */
    public function setProcedure(string $procedure): Client
    {
        $this->procedure = $procedure;
        return $this;
    }

    /**
     * Get procedure template.
     *
     * @access public
     * @return string
     */
    public function getProcedure(): string
    {
        return $this->procedure;
    }

    /**
     * Get procedure compiler.
     *
     * @access public
     * @return ProcedureCompilerInterface
     */
    public function getProcedureCompiler(): ProcedureCompilerInterface
    {
        return $this->procedureCompiler;
    }

    /**
     * Set lazy request flag.
     *
     * @access public
     * @return Client
     */
    public function isLazy(): Client
    {
        $this->procedure = 'http_lazy';
        return $this;
    }
}
